﻿#region Copyright 2011 Mike Goatly
// This source is subject to the the MIT License (MIT)
// All rights reserved.
#endregion

namespace Lifti.Tests.Persistence.PageManagerTests.Transactional
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Lifti.Extensibility;
    using Lifti.Persistence;
    using Lifti.Tests.Persistence.PageManagerTests.Setup;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Moq;

    /// <summary>
    /// The base test class for transactional page manager test classes.
    /// </summary>
    [TestClass]
    public abstract class TransactionalPageManagerTestBase : PageManagerTestBase
    {
        #region Protected Properties
        /// <summary>
        /// Gets or sets the page cache.
        /// </summary>
        /// <value>The page cache.</value>
        protected Mock<IPageCache> PageCache { get; set; }

        /// <summary>
        /// Gets or sets the mocked settings.
        /// </summary>
        /// <value>The settings.</value>
        protected Mock<IPersistenceSettings> Settings { get; set; }

        /// <summary>
        /// Gets or sets the mocked data file manager.
        /// </summary>
        /// <value>The data file manager.</value>
        protected MockDataFileManagerSetup DataFileManager { get; set; }

        /// <summary>
        /// Gets or sets the mocked transaction log factory.
        /// </summary>
        /// <value>The log factory.</value>
        protected Mock<ITransactionLogFactory> LogFactory { get; set; }

        /// <summary>
        /// Gets or sets the mocked transaction log.
        /// </summary>
        /// <value>The transaction log.</value>
        protected Mock<ITransactionLog> TransactionLog { get; set; }

        /// <summary>
        /// Gets or sets the mocked transaction log rollback instance.
        /// </summary>
        /// <value>The transaction log rollback.</value>
        protected Mock<ITransactionLogRollback> TransactionLogRollback { get; set; }

        /// <summary>
        /// Gets or sets the mocked type persistence.
        /// </summary>
        /// <value>The type persistence.</value>
        protected GenericPersistence<int> TypePersistence { get; set; }

        /// <summary>
        /// Gets or sets the mocked index extensibility service.
        /// </summary>
        /// <value>The index extensibility service.</value>
        protected Mock<IIndexExtensibilityService<int>> ExtensibilityService { get; set; }

        /// <summary>
        /// Gets or sets the page manager being tested.
        /// </summary>
        /// <value>The page manager.</value>
        protected TransactionalPageManager<int> PageManager { get; set; }
        #endregion

        /// <summary>
        /// Initializes the class ready for each test to take place.
        /// </summary>
        [TestInitialize]
        public void TestSetup()
        {
            this.Settings = new Mock<IPersistenceSettings>(MockBehavior.Strict);
            this.CreateDataFileManagerMock();

            string transactionId = null;

            this.LogFactory = new Mock<ITransactionLogFactory>(MockBehavior.Strict);
            this.TransactionLog = this.CreateTransactionLogMock();
            this.TransactionLog.SetupGet(l => l.TransactionId).Returns(() => transactionId);

            this.TransactionLogRollback = new Mock<ITransactionLogRollback>();
            this.TypePersistence = new GenericPersistence<int>();
            this.PageCache = CreatePageCacheMock();
            this.ExtensibilityService = new Mock<IIndexExtensibilityService<int>>();
            this.PageManager = new TransactionalPageManager<int>(
                this.CreatePageCacheInstance(),
                this.Settings.Object,
                this.DataFileManager.Mock.Object,
                this.TypePersistence,
                this.LogFactory.Object,
                this.ExtensibilityService.Object);

            this.LogFactory.Setup(f => f.CreateTransactionLog(It.IsAny<string>(), this.PageManager)).Returns(this.TransactionLog.Object).Callback((string id, IPageManager m) => transactionId = id);
            this.LogFactory.Setup(f => f.CreateTransactionLogRollback(this.PageManager)).Returns(this.TransactionLogRollback.Object);

            this.PageManager.Initialize();
        }

        /// <summary>
        /// Creates the mocked data file manager.
        /// </summary>
        protected virtual void CreateDataFileManagerMock()
        {
            this.DataFileManager = new MockDataFileManagerSetup(false)
                .IndexNodePage(0, null, 2, new ItemRefSetup(2, 5, 88).AndNodeRef(6, 4, 'a'))
                .ItemPage(1, null, null, new IndexedItemSetup<int>(1, 3))
                .IndexNodePage(2, 0, 3, new ItemRefSetup(8, 3, 12))
                .IndexNodePage(3, 2, null, new ItemRefSetup(9, 7, 12))
                .Prepare();
        }

        /// <summary>
        /// Resolves the page cache used in the page manager. By default this returns the
        /// mocked page cache, but its behaviour can be overridden to return an actual page
        /// cache instance.
        /// </summary>
        /// <returns>The <see cref="IPageCache"/> instance to use in the page manager.</returns>
        protected virtual IPageCache CreatePageCacheInstance()
        {
            return this.PageCache.Object;
        }

        /// <summary>
        /// Creates the page cache mock.
        /// </summary>
        /// <returns>The mocked page cache.</returns>
        private static Mock<IPageCache> CreatePageCacheMock()
        {
            var cachedHeaders = new Dictionary<int, IDataPageHeader>();
            var cachedDataPages = new Dictionary<int, IDataPage>();

            var pageCache = new Mock<IPageCache>();
            pageCache.Setup(c => c.CacheHeader(It.IsAny<IDataPageHeader>())).Callback((IDataPageHeader h) => cachedHeaders.Add(h.PageNumber, h));
            pageCache.Setup(c => c.CachePage(It.IsAny<IDataPage>())).Callback((IDataPage p) => cachedDataPages.Add(p.Header.PageNumber, p));
            pageCache.Setup(c => c.GetHeader(It.IsAny<int>())).Returns((int n) => cachedHeaders[n]);
            pageCache.Setup(c => c.GetHeader(It.IsAny<int>(), It.IsAny<Func<int, IDataPageHeader>>())).Returns((int p, Func<int, IDataPageHeader> load) =>
                {
                    if (!cachedHeaders.ContainsKey(p))
                    {
                        cachedHeaders[p] = load(p);
                    }

                    return cachedHeaders[p];
                });

            pageCache.Setup(c => c.GetCachedPage(It.IsAny<IDataPageHeader>(), It.IsAny<Func<IDataPageHeader, IDataPage>>())).Returns((IDataPageHeader h, Func<IDataPageHeader, IDataPage> load) =>
            {
                if (!cachedDataPages.ContainsKey(h.PageNumber))
                {
                    cachedDataPages[h.PageNumber] = load(h);
                }

                return cachedDataPages[h.PageNumber];
            });

            pageCache.Setup(c => c.PurgePages(It.IsAny<IEnumerable<int>>())).Callback(
                (IEnumerable<int> p) =>
                {
                    foreach (var page in p)
                    {
                        cachedDataPages.Remove(page);
                    }
                });

            pageCache.Setup(c => c.PurgeHeaders(It.IsAny<IEnumerable<int>>())).Callback(
                (IEnumerable<int> h) =>
                {
                    foreach (var header in h)
                    {
                        cachedHeaders.Remove(header);
                    }
                });

            return pageCache;
        }

        /// <summary>
        /// Creates a mocked transaction log.
        /// </summary>
        /// <returns>The mocked transaction log.</returns>
        private Mock<ITransactionLog> CreateTransactionLogMock()
        {
            var createdPages = new HashSet<IDataPage>();
            var headerWritePages = new HashSet<IDataPage>();
            var bodyWritePages = new HashSet<IDataPage>();
            var pageManagerHeaderWritten = false;

            var transactionLog = new Mock<ITransactionLog>();
            transactionLog.SetupGet(l => l.OriginalPageCount).Returns(4);
            transactionLog.SetupGet(l => l.PageManagerHeaderWritten).Returns(() => pageManagerHeaderWritten);
            transactionLog.SetupSet(l => l.PageManagerHeaderWritten).Callback(v => pageManagerHeaderWritten = v);

            transactionLog.Setup(l => l.RegisterAffectedPage(It.IsAny<IDataPage>(), It.IsAny<PageWriteLevels>())).Callback((IDataPage p, PageWriteLevels l) =>
            {
                switch (l)
                {
                    case PageWriteLevels.Body:
                        bodyWritePages.Add(p);
                        break;

                    case PageWriteLevels.Header:
                        headerWritePages.Add(p);
                        break;

                    case PageWriteLevels.Created:
                        createdPages.Add(p);
                        break;
                }
            });

            transactionLog.SetupGet(l => l.CreatedPageNumbers).Returns(createdPages.Select(p => p.Header.PageNumber));
            transactionLog.SetupGet(l => l.AffectedPages).Returns(createdPages.Union(headerWritePages).Union(bodyWritePages));
            transactionLog.SetupGet(l => l.AffectedPageBodies).Returns(bodyWritePages);
            transactionLog.SetupGet(l => l.AffectedPageHeaders).Returns(headerWritePages);

            return transactionLog;
        }
    }
}
